/*
 * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.transport;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import com.sun.corba.se.pept.broker.Broker;
import com.sun.corba.se.pept.encoding.InputObject;
import com.sun.corba.se.pept.encoding.OutputObject;
import com.sun.corba.se.pept.protocol.MessageMediator;
import com.sun.corba.se.pept.transport.Acceptor;
import com.sun.corba.se.pept.transport.Connection;
import com.sun.corba.se.pept.transport.ContactInfo;
import com.sun.corba.se.pept.transport.EventHandler;
import com.sun.corba.se.pept.transport.InboundConnectionCache;
import com.sun.corba.se.pept.transport.Selector;

import com.sun.corba.se.spi.extension.RequestPartitioningPolicy;
import com.sun.corba.se.spi.ior.IORTemplate;
import com.sun.corba.se.spi.ior.TaggedProfileTemplate;
import com.sun.corba.se.spi.ior.iiop.IIOPAddress;
import com.sun.corba.se.spi.ior.iiop.IIOPFactories;
import com.sun.corba.se.spi.ior.iiop.IIOPProfileTemplate;
import com.sun.corba.se.spi.ior.iiop.GIOPVersion;
import com.sun.corba.se.spi.ior.iiop.AlternateIIOPAddressComponent;
import com.sun.corba.se.spi.logging.CORBALogDomains;
import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.spi.orbutil.threadpool.Work;
import com.sun.corba.se.spi.protocol.CorbaMessageMediator;
import com.sun.corba.se.spi.transport.CorbaAcceptor;
import com.sun.corba.se.spi.transport.CorbaConnection;
import com.sun.corba.se.spi.transport.SocketInfo;
import com.sun.corba.se.spi.transport.SocketOrChannelAcceptor;

import com.sun.corba.se.impl.encoding.CDRInputObject;
import com.sun.corba.se.impl.encoding.CDROutputObject;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;
import com.sun.corba.se.impl.oa.poa.Policies; // REVISIT impl/poa specific
import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.sun.corba.se.impl.orbutil.ORBUtility;

// BEGIN Legacy support.
import com.sun.corba.se.spi.legacy.connection.LegacyServerSocketEndPointInfo;
// END Legacy support.

/**
 * @author Harold Carr
 */
public class SocketOrChannelAcceptorImpl
    extends
    EventHandlerBase
    implements
    CorbaAcceptor,
    SocketOrChannelAcceptor,
    Work,
    // BEGIN Legacy
    SocketInfo,
    LegacyServerSocketEndPointInfo
    // END Legacy
{

  protected ServerSocketChannel serverSocketChannel;
  protected ServerSocket serverSocket;
  protected int port;
  protected long enqueueTime;
  protected boolean initialized;
  protected ORBUtilSystemException wrapper;
  protected InboundConnectionCache connectionCache;

  // BEGIN Legacy
  protected String type = "";
  protected String name = "";
  protected String hostname;
  protected int locatorPort;
  // END Legacy

  public SocketOrChannelAcceptorImpl(ORB orb) {
    this.orb = orb;
    wrapper = ORBUtilSystemException.get(orb,
        CORBALogDomains.RPC_TRANSPORT);

    setWork(this);
    initialized = false;

    // BEGIN Legacy support.
    this.hostname = orb.getORBData().getORBServerHost();
    this.name = LegacyServerSocketEndPointInfo.NO_NAME;
    this.locatorPort = -1;
    // END Legacy support.
  }

  public SocketOrChannelAcceptorImpl(ORB orb, int port) {
    this(orb);
    this.port = port;
  }

  // BEGIN Legacy support.
  public SocketOrChannelAcceptorImpl(ORB orb, int port,
      String name, String type) {
    this(orb, port);
    this.name = name;
    this.type = type;
  }
  // END Legacy support.

  ////////////////////////////////////////////////////
  //
  // pept.transport.Acceptor
  //

  public boolean initialize() {
    if (initialized) {
      return false;
    }
    if (orb.transportDebugFlag) {
      dprint(".initialize: " + this);
    }
    InetSocketAddress inetSocketAddress = null;
    try {
      if (orb.getORBData().getListenOnAllInterfaces()
          .equals(ORBConstants.LISTEN_ON_ALL_INTERFACES)) {
        inetSocketAddress = new InetSocketAddress(port);
      } else {
        String host = orb.getORBData().getORBServerHost();
        inetSocketAddress = new InetSocketAddress(host, port);
      }
      serverSocket = orb.getORBData().getSocketFactory()
          .createServerSocket(type, inetSocketAddress);
      internalInitialize();
    } catch (Throwable t) {
      throw wrapper.createListenerFailed(t, Integer.toString(port));
    }
    initialized = true;
    return true;
  }

  protected void internalInitialize()
      throws Exception {
    // Determine the listening port (for the IOR).
    // This is important when using emphemeral ports (i.e.,
    // when the port value to the constructor is 0).

    port = serverSocket.getLocalPort();

    // Register with transport (also sets up monitoring).

    orb.getCorbaTransportManager().getInboundConnectionCache(this);

    // Finish configuation.

    serverSocketChannel = serverSocket.getChannel();

    if (serverSocketChannel != null) {
      setUseSelectThreadToWait(
          orb.getORBData().acceptorSocketUseSelectThreadToWait());
      serverSocketChannel.configureBlocking(
          !orb.getORBData().acceptorSocketUseSelectThreadToWait());
    } else {
      // Configure to use listener and reader threads.
      setUseSelectThreadToWait(false);
    }
    setUseWorkerThreadForEvent(
        orb.getORBData().acceptorSocketUseWorkerThreadForEvent());

  }

  public boolean initialized() {
    return initialized;
  }

  public String getConnectionCacheType() {
    return this.getClass().toString();
  }

  public void setConnectionCache(InboundConnectionCache connectionCache) {
    this.connectionCache = connectionCache;
  }

  public InboundConnectionCache getConnectionCache() {
    return connectionCache;
  }

  public boolean shouldRegisterAcceptEvent() {
    return true;
  }

  public void accept() {
    try {
      SocketChannel socketChannel = null;
      Socket socket = null;
      if (serverSocketChannel == null) {
        socket = serverSocket.accept();
      } else {
        socketChannel = serverSocketChannel.accept();
        socket = socketChannel.socket();
      }
      orb.getORBData().getSocketFactory()
          .setAcceptedSocketOptions(this, serverSocket, socket);
      if (orb.transportDebugFlag) {
        dprint(".accept: " +
            (serverSocketChannel == null
                ? serverSocket.toString()
                : serverSocketChannel.toString()));
      }

      CorbaConnection connection =
          new SocketOrChannelConnectionImpl(orb, this, socket);
      if (orb.transportDebugFlag) {
        dprint(".accept: new: " + connection);
      }

      // NOTE: The connection MUST be put in the cache BEFORE being
      // registered with the selector.  Otherwise if the bytes
      // are read on the connection it will attempt a time stamp
      // but the cache will be null, resulting in NPE.

      // A connection needs to be timestamped before putting to the cache.
      // Otherwise the newly created connection (with 0 timestamp) could be
      // incorrectly reclaimed by concurrent reclaim() call OR if there
      // will be no events on this connection then it could be reclaimed
      // by upcoming reclaim() call.
      getConnectionCache().stampTime(connection);
      getConnectionCache().put(this, connection);

      if (connection.shouldRegisterServerReadEvent()) {
        Selector selector = orb.getTransportManager().getSelector(0);
        selector.registerForEvent(connection.getEventHandler());
      }

      getConnectionCache().reclaim();

    } catch (IOException e) {
      if (orb.transportDebugFlag) {
        dprint(".accept:", e);
      }
      orb.getTransportManager().getSelector(0).unregisterForEvent(this);
      // REVISIT - need to close - recreate - then register new one.
      orb.getTransportManager().getSelector(0).registerForEvent(this);
      // NOTE: if register cycling we do not want to shut down ORB
      // since local beans will still work.  Instead one will see
      // a growing log file to alert admin of problem.
    }
  }

  public void close() {
    try {
      if (orb.transportDebugFlag) {
        dprint(".close->:");
      }
      Selector selector = orb.getTransportManager().getSelector(0);
      selector.unregisterForEvent(this);
      if (serverSocketChannel != null) {
        serverSocketChannel.close();
      }
      if (serverSocket != null) {
        serverSocket.close();
      }
    } catch (IOException e) {
      if (orb.transportDebugFlag) {
        dprint(".close:", e);
      }
    } finally {
      if (orb.transportDebugFlag) {
        dprint(".close<-:");
      }
    }
  }

  public EventHandler getEventHandler() {
    return this;
  }

  ////////////////////////////////////////////////////
  //
  // CorbaAcceptor
  //

  public String getObjectAdapterId() {
    return null;
  }

  public String getObjectAdapterManagerId() {
    return null;
  }

  public void addToIORTemplate(IORTemplate iorTemplate,
      Policies policies,
      String codebase) {
    Iterator iterator = iorTemplate.iteratorById(
        org.omg.IOP.TAG_INTERNET_IOP.value);

    String hostname = orb.getORBData().getORBServerHost();

    if (iterator.hasNext()) {
      // REVISIT - how does this play with legacy ORBD port exchange?
      IIOPAddress iiopAddress =
          IIOPFactories.makeIIOPAddress(orb, hostname, port);
      AlternateIIOPAddressComponent iiopAddressComponent =
          IIOPFactories.makeAlternateIIOPAddressComponent(iiopAddress);

      while (iterator.hasNext()) {
        TaggedProfileTemplate taggedProfileTemplate =
            (TaggedProfileTemplate) iterator.next();
        taggedProfileTemplate.add(iiopAddressComponent);
      }
    } else {
      GIOPVersion version = orb.getORBData().getGIOPVersion();
      int templatePort;
      if (policies.forceZeroPort()) {
        templatePort = 0;
      } else if (policies.isTransient()) {
        templatePort = port;
      } else {
        templatePort = orb.getLegacyServerSocketManager()
            .legacyGetPersistentServerPort(SocketInfo.IIOP_CLEAR_TEXT);
      }
      IIOPAddress addr =
          IIOPFactories.makeIIOPAddress(orb, hostname, templatePort);
      IIOPProfileTemplate iiopProfile =
          IIOPFactories.makeIIOPProfileTemplate(orb, version, addr);
      if (version.supportsIORIIOPProfileComponents()) {
        iiopProfile.add(IIOPFactories.makeCodeSetsComponent(orb));
        iiopProfile.add(IIOPFactories.makeMaxStreamFormatVersionComponent());
        RequestPartitioningPolicy rpPolicy = (RequestPartitioningPolicy)
            policies.get_effective_policy(
                ORBConstants.REQUEST_PARTITIONING_POLICY);
        if (rpPolicy != null) {
          iiopProfile.add(
              IIOPFactories.makeRequestPartitioningComponent(
                  rpPolicy.getValue()));
        }
        if (codebase != null && codebase != "") {
          iiopProfile.add(IIOPFactories.makeJavaCodebaseComponent(codebase));
        }
        if (orb.getORBData().isJavaSerializationEnabled()) {
          iiopProfile.add(
              IIOPFactories.makeJavaSerializationComponent());
        }
      }
      iorTemplate.add(iiopProfile);
    }
  }

  public String getMonitoringName() {
    return "AcceptedConnections";
  }

  ////////////////////////////////////////////////////
  //
  // EventHandler methods
  //

  public SelectableChannel getChannel() {
    return serverSocketChannel;
  }

  public int getInterestOps() {
    return SelectionKey.OP_ACCEPT;
  }

  public Acceptor getAcceptor() {
    return this;
  }

  public Connection getConnection() {
    throw new RuntimeException("Should not happen.");
  }

  ////////////////////////////////////////////////////
  //
  // Work methods.
  //

    /* CONFLICT: with legacy below.
    public String getName()
    {
        return this.toString();
    }
    */

  public void doWork() {
    try {
      if (orb.transportDebugFlag) {
        dprint(".doWork->: " + this);
      }
      if (selectionKey.isAcceptable()) {
        accept();
      } else {
        if (orb.transportDebugFlag) {
          dprint(".doWork: ! selectionKey.isAcceptable: " + this);
        }
      }
    } catch (SecurityException se) {
      if (orb.transportDebugFlag) {
        dprint(".doWork: ignoring SecurityException: "
            + se
            + " " + this);
      }
      String permissionStr = ORBUtility.getClassSecurityInfo(getClass());
      wrapper.securityExceptionInAccept(se, permissionStr);
    } catch (Exception ex) {
      if (orb.transportDebugFlag) {
        dprint(".doWork: ignoring Exception: "
            + ex
            + " " + this);
      }
      wrapper.exceptionInAccept(ex);
    } catch (Throwable t) {
      if (orb.transportDebugFlag) {
        dprint(".doWork: ignoring Throwable: "
            + t
            + " " + this);
      }
    } finally {

      // IMPORTANT: To avoid bug (4953599), we force the
      // Thread that does the NIO select to also do the
      // enable/disable of Ops using SelectionKey.interestOps().
      // Otherwise, the SelectionKey.interestOps() may block
      // indefinitely.
      // NOTE: If "acceptorSocketUseWorkerThreadForEvent" is
      // set to to false in ParserTable.java, then this method,
      // doWork(), will get executed by the same thread
      // (SelectorThread) that does the NIO select.
      // If "acceptorSocketUseWorkerThreadForEvent" is set
      // to true, a WorkerThread will execute this method,
      // doWork(). Hence, the registering of the enabling of
      // the SelectionKey's interestOps is done here instead
      // of calling SelectionKey.interestOps(<interest op>).

      Selector selector = orb.getTransportManager().getSelector(0);
      selector.registerInterestOps(this);

      if (orb.transportDebugFlag) {
        dprint(".doWork<-:" + this);
      }
    }
  }

  public void setEnqueueTime(long timeInMillis) {
    enqueueTime = timeInMillis;
  }

  public long getEnqueueTime() {
    return enqueueTime;
  }

  //
  // Factory methods.
  //

  // REVISIT: refactor into common base or delegate.
  public MessageMediator createMessageMediator(Broker broker,
      Connection connection) {
    // REVISIT - no factoring so cheat to avoid code dup right now.
    // REVISIT **** COUPLING !!!!
    ContactInfo contactInfo = new SocketOrChannelContactInfoImpl();
    return contactInfo.createMessageMediator(broker, connection);
  }

  // REVISIT: refactor into common base or delegate.
  public MessageMediator finishCreatingMessageMediator(Broker broker,
      Connection connection,
      MessageMediator messageMediator) {
    // REVISIT - no factoring so cheat to avoid code dup right now.
    // REVISIT **** COUPLING !!!!
    ContactInfo contactInfo = new SocketOrChannelContactInfoImpl();
    return contactInfo.finishCreatingMessageMediator(broker,
        connection, messageMediator);
  }

  public InputObject createInputObject(Broker broker,
      MessageMediator messageMediator) {
    CorbaMessageMediator corbaMessageMediator = (CorbaMessageMediator)
        messageMediator;
    return new CDRInputObject((ORB) broker,
        (CorbaConnection) messageMediator.getConnection(),
        corbaMessageMediator.getDispatchBuffer(),
        corbaMessageMediator.getDispatchHeader());
  }

  public OutputObject createOutputObject(Broker broker,
      MessageMediator messageMediator) {
    CorbaMessageMediator corbaMessageMediator = (CorbaMessageMediator)
        messageMediator;
    return sun.corba.OutputStreamFactory.newCDROutputObject((ORB) broker,
        corbaMessageMediator, corbaMessageMediator.getReplyHeader(),
        corbaMessageMediator.getStreamFormatVersion());
  }

  ////////////////////////////////////////////////////
  //
  // SocketOrChannelAcceptor
  //

  public ServerSocket getServerSocket() {
    return serverSocket;
  }

  ////////////////////////////////////////////////////
  //
  // Implementation.
  //

  public String toString() {
    String sock;
    if (serverSocketChannel == null) {
      if (serverSocket == null) {
        sock = "(not initialized)";
      } else {
        sock = serverSocket.toString();
      }
    } else {
      sock = serverSocketChannel.toString();
    }

    return
        toStringName() +
            "["
            + sock + " "
            + type + " "
            + shouldUseSelectThreadToWait() + " "
            + shouldUseWorkerThreadForEvent()
            + "]";
  }

  protected String toStringName() {
    return "SocketOrChannelAcceptorImpl";
  }

  protected void dprint(String msg) {
    ORBUtility.dprint(toStringName(), msg);
  }

  protected void dprint(String msg, Throwable t) {
    dprint(msg);
    t.printStackTrace(System.out);
  }

  // BEGIN Legacy support
  ////////////////////////////////////////////////////
  //
  // LegacyServerSocketEndPointInfo and EndPointInfo
  //

  public String getType() {
    return type;
  }

  public String getHostName() {
    return hostname;
  }

  public String getHost() {
    return hostname;
  }

  public int getPort() {
    return port;
  }

  public int getLocatorPort() {
    return locatorPort;
  }

  public void setLocatorPort(int port) {
    locatorPort = port;
  }

  public String getName() {
    // Kluge alert:
    // Work and Legacy both define getName.
    // Try to make this behave best for most cases.
    String result =
        name.equals(LegacyServerSocketEndPointInfo.NO_NAME) ?
            this.toString() : name;
    return result;
  }
  // END Legacy support
}

// End of file.
